#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
干净的文档转换引擎

专注于转换功能，支持：
- 可配置的OBS上传功能
- 可配置的文件管理策略（保留/删除）
- 调试模式支持
- 清晰的配置接口
"""

import asyncio
import uuid
import logging
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Dict, Any, Tuple
from dataclasses import dataclass, field
import shutil

# 导入转换器组件
from .converters.converter_manager import converter_manager
from .converters.base_converter import FileInfo
from .converters.work_dir_config import work_dir_config

logger = logging.getLogger(__name__)

@dataclass
class FileManagementConfig:
    """文件管理配置"""
    # 是否保留工作目录文件
    keep_work_files: bool = False
    
    # 调试模式 - 总是保留文件
    debug_mode: bool = False
    
    # 只保留特定类型的文件
    keep_only_types: Optional[List[str]] = None  # ["image", "document", "markdown"]
    
    # 自定义清理策略
    custom_cleanup_func: Optional[Any] = None

@dataclass 
class ObsUploadConfig:
    """OBS上传配置"""
    # 是否启用OBS上传
    enabled: bool = False
    
    # OBS配置信息
    obs_config: Optional[Dict[str, Any]] = None
    
    # 只上传特定类型的文件
    upload_types: List[str] = field(default_factory=lambda: ["image"])
    
    # OBS存储路径前缀
    storage_prefix: str = "md"
    
    # 是否替换markdown中的链接
    replace_links: bool = True
    
    # 当没有启用OBS上传时，是否清除图片链接（默认启用）
    remove_local_image_links: bool = True

@dataclass
class ConversionConfig:
    """转换配置"""
    # 工作目录配置
    work_temp_dir: Optional[str] = None
    
    # 文件管理配置
    file_management: FileManagementConfig = field(default_factory=FileManagementConfig)
    
    # OBS上传配置
    obs_upload: ObsUploadConfig = field(default_factory=ObsUploadConfig)
    
    # 日志级别
    log_level: str = "INFO"

@dataclass
class UploadedFile:
    """上传的文件信息"""
    file_name: str
    file_size: int
    upload_url: str
    upload_type: str  # "obs", "local"
    object_key: Optional[str] = None

@dataclass
class ConversionResult:
    """转换结果"""
    file_id: str
    markdown_content: str
    local_files: List[FileInfo]  # 本地生成的文件
    uploaded_files: List[UploadedFile]  # 上传的文件
    success: bool
    error_message: Optional[str] = None
    work_dir: Optional[Path] = None  # 工作目录路径，调试时有用

class CleanConverter:
    """干净的文档转换引擎"""
    
    def __init__(self, config: Optional[ConversionConfig] = None):
        """
        初始化转换器
        
        Args:
            config: 转换配置，如果为None则使用默认配置
        """
        self.config = config or ConversionConfig()
        
        # 设置日志级别
        logging.getLogger().setLevel(getattr(logging, self.config.log_level.upper()))
        
        # 初始化工作目录配置
        work_dir_config.initialize(self.config.work_temp_dir)
        
        # 设置工作目录调试模式
        debug_mode = (
            self.config.file_management.debug_mode or 
            self.config.file_management.keep_work_files
        )
        work_dir_config.set_debug_mode(debug_mode)
        
        # 同步设置转换器管理器的调试模式
        converter_manager.set_debug_mode(debug_mode)
        
        # 初始化OBS上传器
        self.obs_uploader = None
        if self.config.obs_upload.enabled:
            self._init_obs_uploader()
        
        logger.info(f"转换引擎初始化完成")
        logger.info(f"- 工作目录: {work_dir_config.get_base_work_dir()}")
        logger.info(f"- 文件保留: {self.config.file_management.keep_work_files}")
        logger.info(f"- 调试模式: {self.config.file_management.debug_mode}")
        logger.info(f"- OBS上传: {self.config.obs_upload.enabled}")
    
    def _init_obs_uploader(self):
        """初始化OBS上传器"""
        try:
            from .uploader import ObsUploader
            
            self.obs_uploader = ObsUploader(self.config.obs_upload.obs_config)
            if self.obs_uploader.is_available():
                logger.info("OBS上传器初始化成功")
            else:
                logger.warning("OBS上传器已创建，但服务不可用")
                self.obs_uploader = None
        except ImportError:
            logger.warning("OBS上传器模块未找到，OBS上传功能将不可用")
            self.obs_uploader = None
        except Exception as e:
            logger.warning(f"OBS上传器初始化失败: {e}")
            self.obs_uploader = None
    
    def generate_file_id(self) -> str:
        """生成唯一文件ID"""
        now = datetime.now()
        date_part = now.strftime("%Y%m%d_%H%M%S")
        uuid_part = str(uuid.uuid4())[:8]
        return f"{date_part}_{uuid_part}"
    
    async def convert_file(
        self, 
        file_path: str, 
        conversion_method: Optional[str] = None,
        **kwargs
    ) -> ConversionResult:
        """
        转换单个文件
        
        Args:
            file_path: 文件路径
            conversion_method: 转换方法（可选）
            **kwargs: 其他转换参数
            
        Returns:
            ConversionResult: 转换结果
        """
        file_path_obj = Path(file_path)
        file_id = self.generate_file_id()
        
        logger.info(f"开始转换文件: {file_path_obj.name} (ID: {file_id})")
        
        # 检查文件支持
        if not file_path_obj.exists():
            return self._create_error_result(file_id, f"文件不存在: {file_path}")
        
        converter_type = converter_manager.get_converter_type(file_path_obj)
        if not converter_type:
            supported_formats = list(converter_manager._ext_to_converter.keys())
            return self._create_error_result(
                file_id, 
                f"不支持的文件格式: {file_path_obj.suffix}。支持的格式: {', '.join(supported_formats)}"
            )
        
        work_dir = None
        try:
            # 获取转换器实例
            converter = converter_manager.get_converter(converter_type, conversion_method, file_id)
            work_dir = work_dir_config.create_file_work_dir(file_id)
            
            # 检查转换器是否有可用方法
            available_methods = converter.get_available_methods()
            if not available_methods:
                return self._create_error_result(
                    file_id,
                    f"无法转换 {file_path_obj.suffix} 文件，缺少必要的依赖",
                    work_dir
                )
            
            # 执行转换
            try:
                markdown_content, file_infos = await converter.convert(
                    file_path_obj, 
                    method=conversion_method,
                    **kwargs
                )
            except Exception as e:
                return self._create_error_result(file_id, f"转换失败: {str(e)}", work_dir)
            
            # 处理文件上传和链接处理
            uploaded_files = []
            if self.config.obs_upload.enabled and self.obs_uploader:
                # OBS上传已启用
                uploaded_files = await self._upload_files(file_id, file_infos)
                
                # 替换markdown中的链接为OBS链接
                if self.config.obs_upload.replace_links and uploaded_files:
                    markdown_content = self._update_markdown_links(markdown_content, uploaded_files)
            else:
                # OBS上传未启用，检查是否需要清除本地图片链接
                if self.config.obs_upload.remove_local_image_links:
                    markdown_content = self._remove_local_image_links(markdown_content)
            
            # 创建成功结果
            result = ConversionResult(
                file_id=file_id,
                markdown_content=markdown_content,
                local_files=file_infos,
                uploaded_files=uploaded_files,
                success=True,
                work_dir=work_dir
            )
            
            # 处理文件清理
            await self._handle_file_cleanup(file_id, file_infos)
            
            logger.info(f"文件转换成功: {file_path_obj.name} (ID: {file_id})")
            logger.info(f"- 本地文件: {len(file_infos)} 个")
            logger.info(f"- 上传文件: {len(uploaded_files)} 个")
            
            return result
            
        except Exception as e:
            error_msg = f"转换过程异常: {str(e)}"
            logger.error(error_msg)
            
            # 如果出错，根据配置决定是否清理工作目录
            if work_dir and not self.config.file_management.debug_mode:
                await self._cleanup_work_dir(file_id)
            
            return self._create_error_result(file_id, error_msg, work_dir)
    
    async def convert_files(
        self, 
        file_paths: List[str],
        conversion_methods: Optional[Dict[str, str]] = None,
        **kwargs
    ) -> List[ConversionResult]:
        """
        批量转换文件
        
        Args:
            file_paths: 文件路径列表
            conversion_methods: 文件特定的转换方法映射
            **kwargs: 其他转换参数
            
        Returns:
            List[ConversionResult]: 转换结果列表
        """
        conversion_methods = conversion_methods or {}
        
        logger.info(f"开始批量转换 {len(file_paths)} 个文件")
        
        # 并发执行转换
        tasks = []
        for file_path in file_paths:
            method = conversion_methods.get(file_path)
            task = self.convert_file(file_path, method, **kwargs)
            tasks.append(task)
        
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # 处理异常结果
        processed_results = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                file_id = self.generate_file_id()
                error_result = ConversionResult(
                    file_id=file_id,
                    markdown_content="",
                    local_files=[],
                    uploaded_files=[],
                    success=False,
                    error_message=f"转换异常: {str(result)}"
                )
                processed_results.append(error_result)
                logger.error(f"文件 {file_paths[i]} 转换异常: {result}")
            else:
                processed_results.append(result)
        
        successful_count = sum(1 for r in processed_results if r.success)
        logger.info(f"批量转换完成: {successful_count}/{len(file_paths)} 个文件成功")
        
        return processed_results
    
    async def _upload_files(self, file_id: str, file_infos: List[FileInfo]) -> List[UploadedFile]:
        """上传文件到OBS"""
        if not self.obs_uploader:
            return []
        
        uploaded_files = []
        upload_types = self.config.obs_upload.upload_types
        
        # 过滤需要上传的文件
        files_to_upload = [f for f in file_infos if f.file_type in upload_types]
        
        if not files_to_upload:
            logger.info("没有需要上传的文件")
            return uploaded_files
        
        logger.info(f"准备上传 {len(files_to_upload)} 个文件到OBS")
        
        for file_info in files_to_upload:
            try:
                # 构建OBS对象键
                obs_key = f"{self.config.obs_upload.storage_prefix}/{file_id}/{file_info.file_name}"
                
                # 检查文件是否存在
                file_path = Path(file_info.file_path)
                if not file_path.exists():
                    logger.warning(f"文件不存在，跳过上传: {file_info.file_path}")
                    continue
                
                # 上传文件
                result = await self.obs_uploader.upload_file(
                    file_info.file_path, 
                    obs_key
                )
                
                if result.success and result.file_url:
                    uploaded_file = UploadedFile(
                        file_name=file_info.file_name,
                        file_size=file_info.file_size,
                        upload_url=result.file_url,
                        upload_type="obs",
                        object_key=obs_key
                    )
                    uploaded_files.append(uploaded_file)
                    logger.info(f"文件已上传到OBS: {file_info.file_name} -> {result.file_url}")
                else:
                    error_msg = result.error if result.error else "未知错误"
                    logger.warning(f"文件上传失败: {file_info.file_name}, 错误: {error_msg}")
                    
            except Exception as e:
                logger.warning(f"上传文件 {file_info.file_name} 到OBS失败: {e}")
        
        logger.info(f"成功上传 {len(uploaded_files)} 个文件到OBS")
        return uploaded_files
    
    def _update_markdown_links(self, markdown_content: str, uploaded_files: List[UploadedFile]) -> str:
        """更新markdown中的链接为上传后的URL"""
        if not uploaded_files:
            return markdown_content
        
        # 创建文件名到URL的映射
        filename_to_url = {f.file_name: f.upload_url for f in uploaded_files}
        
        # 使用正则表达式替换图片链接
        import re
        import urllib.parse
        
        updated_content = markdown_content
        replacement_count = 0
        
        for filename, upload_url in filename_to_url.items():
            # 生成文件名的不同编码形式
            encoded_filename = urllib.parse.quote(filename)
            
            # 匹配各种Markdown图片语法
            patterns = [
                # ![alt text](filename)
                (re.compile(rf'!\[([^\]]*)\]\(([^)]*{re.escape(filename)}[^)]*)\)', re.IGNORECASE), 
                 rf'![\1]({upload_url})'),
                # ![alt text](encoded_filename)
                (re.compile(rf'!\[([^\]]*)\]\(([^)]*{re.escape(encoded_filename)}[^)]*)\)', re.IGNORECASE), 
                 rf'![\1]({upload_url})'),
                # 相对路径引用
                (re.compile(rf'!\[([^\]]*)\]\(([^)]*[./]*[^/]*/{re.escape(filename)}[^)]*)\)', re.IGNORECASE),
                 rf'![\1]({upload_url})'),
            ]
            
            for pattern, replacement in patterns:
                new_content, count = pattern.subn(replacement, updated_content)
                if count > 0:
                    replacement_count += count
                    updated_content = new_content
                    logger.debug(f"替换了 {count} 个 {filename} 的引用")
        
        if replacement_count > 0:
            logger.info(f"完成Markdown链接更新，共替换 {replacement_count} 个链接")
        
        return updated_content

    def _remove_local_image_links(self, markdown_content: str) -> str:
        """移除markdown中的本地图片链接"""
        import re
        
        # 匹配各种Markdown图片语法：![alt text](path)
        image_pattern = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)', re.IGNORECASE)
        
        # 统计移除前的图片链接数量
        original_matches = image_pattern.findall(markdown_content)
        original_count = len(original_matches)
        
        if original_count == 0:
            logger.debug("Markdown中没有找到图片链接")
            return markdown_content
        
        # 移除所有图片链接，但保留alt文本（如果有的话）
        def replace_image_link(match):
            alt_text = match.group(1).strip()
            if alt_text:
                # 如果有alt文本，保留为普通文本
                return f"*[图片: {alt_text}]*"
            else:
                # 如果没有alt文本，用通用标记
                return "*[图片]*"
        
        updated_content = image_pattern.sub(replace_image_link, markdown_content)
        
        # 统计移除后的结果
        remaining_matches = re.findall(r'!\[([^\]]*)\]\(([^)]+)\)', updated_content)
        remaining_count = len(remaining_matches)
        removed_count = original_count - remaining_count
        
        if removed_count > 0:
            logger.info(f"已移除 {removed_count} 个本地图片链接，保留为文本标记")
            # 显示一些示例
            for i, (alt_text, path) in enumerate(original_matches[:3], 1):
                logger.debug(f"  示例 {i}: ![{alt_text}]({path}) -> *[图片{': ' + alt_text if alt_text else ''}]*")
        
        return updated_content
    
    async def _handle_file_cleanup(self, file_id: str, file_infos: List[FileInfo]):
        """处理文件清理"""
        # 调试模式总是保留文件
        if self.config.file_management.debug_mode:
            logger.info(f"调试模式，保留工作目录: {work_dir_config.get_base_work_dir() / file_id}")
            return
        
        # 如果配置为保留文件，则不清理
        if self.config.file_management.keep_work_files:
            logger.info(f"配置为保留工作文件: {work_dir_config.get_base_work_dir() / file_id}")
            return
        
        # 如果有自定义清理策略，使用自定义策略
        if self.config.file_management.custom_cleanup_func:
            try:
                await self.config.file_management.custom_cleanup_func(file_id, file_infos)
                return
            except Exception as e:
                logger.warning(f"自定义清理策略执行失败: {e}，使用默认清理策略")
        
        # 默认清理策略：删除工作目录
        await self._cleanup_work_dir(file_id)
    
    async def _cleanup_work_dir(self, file_id: str):
        """清理工作目录"""
        try:
            work_dir = work_dir_config.get_base_work_dir() / file_id
            if work_dir.exists():
                shutil.rmtree(work_dir)
                logger.debug(f"已清理工作目录: {work_dir}")
        except Exception as e:
            logger.warning(f"清理工作目录失败: {e}")
    
    def _create_error_result(
        self, 
        file_id: str, 
        error_message: str, 
        work_dir: Optional[Path] = None
    ) -> ConversionResult:
        """创建错误结果"""
        return ConversionResult(
            file_id=file_id,
            markdown_content="",
            local_files=[],
            uploaded_files=[],
            success=False,
            error_message=error_message,
            work_dir=work_dir
        )
    
    # 工具方法
    def get_converter_status(self) -> Dict[str, Any]:
        """获取转换器状态信息"""
        from .core import DocumentConverter
        temp_converter = DocumentConverter()
        return temp_converter.get_available_converters_info()
    
    def get_work_dir_status(self) -> Dict[str, Any]:
        """获取工作目录状态"""
        return work_dir_config.get_status()
    
    def cleanup_all_work_dirs(self):
        """清理所有工作目录"""
        work_dir_config.cleanup_all_work_dirs()
    
    def update_config(self, **kwargs):
        """动态更新配置"""
        for key, value in kwargs.items():
            if hasattr(self.config, key):
                setattr(self.config, key, value)
                logger.info(f"更新配置 {key} = {value}")
            else:
                logger.warning(f"未知配置项: {key}") 